package cn.xinfei.xdecision.engine.runner.service.impl;

import cn.xinfei.xdecision.common.datadao.DecisionResultMapper;
import cn.xinfei.xdecision.common.datamodel.DecisionResult;
import cn.xinfei.xdecision.constant.EngineResultEnum;
import cn.xinfei.xdecision.engine.runner.constant.XEngineConstant;
import cn.xinfei.xdecision.engine.runner.context.BranchTypeEnum;
import cn.xinfei.xdecision.engine.runner.context.PipelineContext;
import cn.xinfei.xdecision.engine.runner.context.PipelineContextHolder;
import cn.xinfei.xdecision.engine.runner.context.XDecisionContext;
import cn.xinfei.xdecision.engine.runner.executor.model.StateTransmitModel;
import cn.xinfei.xdecision.engine.runner.producer.DecisionProducer;
import cn.xinfei.xdecision.engine.runner.service.DecisionResultDetailService;
import cn.xinfei.xdecision.engine.runner.service.DecisionResultService;
import cn.xinfei.xdecision.model.DecisionReentryApiRequest;
import cn.xinfei.xdecision.redis.RedisLock;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dianping.cat.Cat;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import io.micrometer.core.instrument.Metrics;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class DecisionResultServiceImpl extends ServiceImpl<DecisionResultMapper, DecisionResult> implements DecisionResultService {

    @Autowired
    private DecisionResultMapper decisionResultMapper;

    @Autowired
    private DecisionResultDetailService decisionResultDetailService;

    @Autowired
    private DecisionProducer decisionProducer;

    @Value("${xdecision.decisionresult.decisionSnapshotTopic}")
    private String decisionSnapshotTopic;


    @Override
    public boolean updateDecisionResultCodeById(String bizId, String decisionId, String code) {
        List<DecisionResult> decisionResults = null;
        decisionResults = getDecisionResultByBizIdAndDecisionId(bizId, decisionId);
        /**
         * 此处没有批量改，而是先查询，然后单个update
         */
        if (CollectionUtils.isEmpty(decisionResults) == false) {
            decisionResults.forEach(decisionResult -> {
                decisionResult.setCode(code);
                LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
                if (StringUtils.isNotEmpty(decisionResult.getRequestId())) {
                    wrapper.eq(DecisionResult::getRequestId, decisionResult.getRequestId());
                }
                if (StringUtils.isNotEmpty(decisionResult.getDecisionId())) {
                    wrapper.eq(DecisionResult::getDecisionId, decisionResult.getDecisionId());
                }
                int update = decisionResultMapper.update(decisionResult, wrapper);
                log.info("updateDecisionResultCodeById result={}, bizId={}, decisionId={},  code={}", update, bizId, decisionId, code);
            });
        }
        return true;
    }

    @Override
    public boolean updateDecisionResultObjectId(String bizId, String decisionId, String ObjectId) {
        return false;
    }

    @Override
    public PageInfo<DecisionResult> getMainRequestIdsByReentryRequest(DecisionReentryApiRequest decisionReentryApiRequest, List<String> states, Date startTime, Date endTime, ConcurrentHashMap<String, Long> processingIds) {

        PageMethod.startPage(decisionReentryApiRequest.getPageStart(), decisionReentryApiRequest.getPageSize());


        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
//        wrapper.select(DecisionResult::getId);//只查requestId
//        wrapper.select(DecisionResult::getRequestId,DecisionResult::getDecisionId);//只查requestId
        wrapper.select(DecisionResult::getId,DecisionResult::getMainDecisionId);//只查getMainDecisionId
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getRequestId())) {
            wrapper.eq(DecisionResult::getRequestId, decisionReentryApiRequest.getRequestId());
        }
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getDecisionId())) {
            wrapper.eq(DecisionResult::getDecisionId, decisionReentryApiRequest.getDecisionId());
        }
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getMainDecisionId())) {
            wrapper.eq(DecisionResult::getMainDecisionId, decisionReentryApiRequest.getMainDecisionId());
        }
        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(states)) {
            wrapper.in(DecisionResult::getCode, states);
        }

        if (startTime != null) {
            wrapper.ge(DecisionResult::getCreateTime, startTime);
        }

        if (endTime != null) {
            wrapper.le(DecisionResult::getCreateTime, endTime);
        }
        String sync = decisionReentryApiRequest.getSync();
        if (StringUtils.isNotEmpty(sync)) {
            wrapper.eq(DecisionResult::getSync, sync);
        }

        if (null != processingIds && processingIds.size() > 0) {
            log.info("processing size={}", processingIds.size());
            Collection<Long> values = processingIds.values();
            wrapper.notIn(DecisionResult::getId, values);
        }
        wrapper.ne(DecisionResult::getMainDecisionId, 0);
        //只查man分支
        wrapper.eq(DecisionResult::getBranch, BranchTypeEnum.MAIN.name());
        wrapper.le(DecisionResult::getRetry, decisionReentryApiRequest.getRetry());
        wrapper.last("and id % " + decisionReentryApiRequest.getShardTotal() + "=" + decisionReentryApiRequest.getShardIndex() + " order by update_time,retry");

        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        PageInfo pageInfo = new PageInfo<>(decisionResults);

        if (decisionResults == null || decisionResults.isEmpty()) {
            return null;
        }
        pageInfo.setTotal(pageInfo.getTotal());
        return pageInfo;
    }

    @Override
    public PageInfo<DecisionResult> getRequestIdsByReentryRequest(DecisionReentryApiRequest decisionReentryApiRequest, List<String> states, Date startTime, Date endTime, ConcurrentHashMap<String, Long> processingIds) {

        PageMethod.startPage(decisionReentryApiRequest.getPageStart(), decisionReentryApiRequest.getPageSize());


        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
//        wrapper.select(DecisionResult::getId);//只查requestId
//        wrapper.select(DecisionResult::getRequestId,DecisionResult::getDecisionId);//只查requestId
        wrapper.select(DecisionResult::getRequestId);//只查requestId
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getRequestId())) {
            wrapper.eq(DecisionResult::getRequestId, decisionReentryApiRequest.getRequestId());
        }
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getDecisionId())) {
            wrapper.eq(DecisionResult::getDecisionId, decisionReentryApiRequest.getDecisionId());
        }
        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(states)) {
            wrapper.in(DecisionResult::getCode, states);
        }

        if (startTime != null) {
            wrapper.ge(DecisionResult::getCreateTime, startTime);
        }

        if (endTime != null) {
            wrapper.le(DecisionResult::getCreateTime, endTime);
        }
        String sync = decisionReentryApiRequest.getSync();
        if (StringUtils.isNotEmpty(sync)) {
            wrapper.le(DecisionResult::getSync, sync);
        }

        if (null != processingIds && processingIds.size() > 0) {
            log.info("processing size={}", processingIds.size());
            Collection<Long> values = processingIds.values();
            wrapper.notIn(DecisionResult::getId, values);
        }

        //只查man分支
        wrapper.eq(DecisionResult::getBranch, BranchTypeEnum.MAIN.name());
        wrapper.le(DecisionResult::getRetry, decisionReentryApiRequest.getRetry());
        wrapper.last("and id % " + decisionReentryApiRequest.getShardTotal() + "=" + decisionReentryApiRequest.getShardIndex() + " order by update_time,retry");

        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        PageInfo pageInfo = new PageInfo<>(decisionResults);

        if (decisionResults == null || decisionResults.isEmpty()) {
            return null;
        }
        pageInfo.setTotal(pageInfo.getTotal());
        return pageInfo;
    }

    @Override
    public PageInfo<DecisionResult> getDecisionResultByBizId(DecisionReentryApiRequest decisionReentryApiRequest) {

        PageHelper.startPage(decisionReentryApiRequest.getPageStart(), decisionReentryApiRequest.getPageSize());


        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getRequestId())) {
            wrapper.eq(DecisionResult::getRequestId, decisionReentryApiRequest.getRequestId());
        }
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getDecisionId())) {
            wrapper.eq(DecisionResult::getDecisionId, decisionReentryApiRequest.getDecisionId());
        }
        if (StringUtils.isNotEmpty(decisionReentryApiRequest.getCode())) {
            wrapper.eq(DecisionResult::getCode, decisionReentryApiRequest.getCode());
        }

        if (decisionReentryApiRequest.getStart() != null) {
            wrapper.ge(DecisionResult::getUpdateTime, decisionReentryApiRequest.getStart());
        }

        if (decisionReentryApiRequest.getEnd() != null) {
            wrapper.le(DecisionResult::getUpdateTime, decisionReentryApiRequest.getEnd());
        }

        wrapper.le(DecisionResult::getRetry, decisionReentryApiRequest.getRetry());

        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        PageInfo pageInfo = new PageInfo<>(decisionResults);

        if (decisionResults == null || decisionResults.isEmpty()) {
            return null;
        }
        pageInfo.setTotal(pageInfo.getTotal());
        return pageInfo;


    }

    @Override
    public DecisionResult saveDecisionResultByBizId(DecisionResult decisionResult) {
        int insert = decisionResultMapper.insert(decisionResult);
        String decisionId = decisionResult.getDecisionId();
//        log.info("saveDecisionResult decisionId={},insert={}", decisionId, insert);
        return decisionResult;
    }

    @Override
    public List<DecisionResult> getDecisionResultByBizId(String bizId, String scene, String engineCode) {
        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DecisionResult::getRequestId, bizId);
        wrapper.eq(DecisionResult::getScene, scene);
        wrapper.eq(DecisionResult::getEngineCode, engineCode);
        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        return decisionResults;
    }

    @Override
    public List<DecisionResult> getDecisionResultByBizId(String bizId) {

        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(DecisionResult::getId, DecisionResult::getDecisionId, DecisionResult::getRequestId, DecisionResult::getBranch, DecisionResult::getEngineCode, DecisionResult::getParentDecisionId, DecisionResult::getCode, DecisionResult::getTpCode);


        wrapper.eq(DecisionResult::getRequestId, bizId);
        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        return decisionResults;
    }

    @Override
    public List<DecisionResult> getDecisionResultByBizIdAndDecisionId(String bizId, String decisionId) {

        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DecisionResult::getRequestId, bizId);
        if (StringUtils.isNotEmpty(decisionId)) {
            wrapper.eq(DecisionResult::getDecisionId, decisionId);
        }

        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        return decisionResults;
    }

    @Override
    public DecisionResult selectDecisionResultByDecisionId(String bizId, String decisionId) {
        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(DecisionResult::getCode);
        wrapper.eq(DecisionResult::getRequestId, bizId);
        wrapper.eq(DecisionResult::getDecisionId, decisionId);
        DecisionResult decisionResults = decisionResultMapper.selectOne(wrapper);
        return decisionResults;
    }

    @Override
    public List<DecisionResult> getDecisionResultByMainDecisionId(String decisionId) {
        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DecisionResult::getMainDecisionId, decisionId);
        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        return decisionResults;
    }
    @Override
    public List<DecisionResult> getDecisionResultByDecisionId(String decisionId) {

        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DecisionResult::getDecisionId, decisionId);
        List<DecisionResult> decisionResults = decisionResultMapper.selectList(wrapper);

        return decisionResults;
    }

    @Transactional(rollbackFor = Exception.class, transactionManager = "transactionManagerData")
    @Override
    public DecisionResult upsertDecisionResultByDecisionId(DecisionResult decisionResult,String executorDecisionId,String updateDecisionId) throws Exception {

        String decisionId = decisionResult.getDecisionId();//等于updateDecisionId
        LambdaQueryWrapper<DecisionResult> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DecisionResult::getDecisionId, updateDecisionId);
        int update = decisionResultMapper.update(decisionResult, wrapper);
        if (update == 0) {
            int insert = decisionResultMapper.insert(decisionResult);
//            RedisLock.setbit(XEngineConstant.IDEMPOTENT_KEY,Math.abs(decisionResult.getRequestId().hashCode()),true);
            log.info("upsertResult success.decisionId={},insert={},id={},insert={}", decisionId, update, decisionResult.getId(),insert);
        } else {
            log.info("updateResult success.decisionId={},update={}", decisionId, update);
        }

        try {
            XDecisionContext context = XDecisionContext.getContext();
            PipelineContext pipelineContext = context.getPipelineContext();
            //这个位置改变decisionId和engineCode是有风险的,目前没有问题
            pipelineContext.setDecisionId(decisionId);
            pipelineContext.setEngineCode(decisionResult.getEngineCode());
//            if (pipelineContext.getTpCode().contains("shiyusen")) {
//                throw new Exception("事务测试日志");
//            }
            boolean b = decisionResultDetailService.upsertDecisionResultDetailLog(pipelineContext,executorDecisionId, updateDecisionId);
            if (b == false) {
                log.error("upsertResultDetail fail.result={}", b);
//                RedisLock.setbit(XEngineConstant.IDEMPOTENT_KEY,Math.abs(decisionResult.getRequestId().hashCode()),false);
                throw new Exception("mongo记录更新失败");
            }
            log.info("upsertResultDetail success.");
        } catch (Exception exception) {
            log.error("updateDetail fail.decisionResult={}", decisionResult, exception);
//            RedisLock.setbit(XEngineConstant.IDEMPOTENT_KEY,Math.abs(decisionResult.getRequestId().hashCode()),false);
            Cat.logError(exception);
            Metrics.counter("xengine_apply_exception_total").increment();
            throw exception;
        }
        return decisionResult;
    }


    @Transactional(rollbackFor = Exception.class, transactionManager = "transactionManagerData")
    @Override
    public DecisionResult saveDecisionResultByDecisionId(DecisionResult decisionResult) throws Exception {

        int insert = decisionResultMapper.insert(decisionResult);
        log.info("saveResult success.decisionId={},insert={}", decisionResult.getDecisionId(), insert);

        try {
            EngineResultEnum engineResultEnum = PipelineContextHolder.getEngineResult();
            XDecisionContext context = XDecisionContext.getContext();
            PipelineContext pipelineContext = context.getPipelineContext();
            pipelineContext.setRespond(decisionResult.getRespond());
            pipelineContext.setState(engineResultEnum.getState());
            pipelineContext.setResult(engineResultEnum.getResult());
            pipelineContext.setCode(engineResultEnum.getCode());
            boolean b = decisionResultDetailService.saveDecisionResultDetail(pipelineContext);
            if (b == false) {
                log.error("saveDetail fail.result={}", b);
                throw new Exception("mongo记录更新失败");
            }
            log.info("saveDetail success.result={}", b);
//            if(decisionResult.getTpCode().contains("shiyusen")){
//                throw new Exception("事务测试日志");
//            }
        } catch (Exception exception) {
            log.error("saveDetail fail.decisionResult={}", decisionResult, exception);
            Cat.logError(exception);
            Metrics.counter("xengine_apply_exception_total").increment();
            throw exception;
        }
        return decisionResult;
    }

    @Override
    public DecisionResult updateDecisionResultDetailByDecisionId(DecisionResult decisionResult) {
        return null;
    }
}
